package gao.xiaolei.service;

import gao.xiaolei.dto.*;
import gao.xiaolei.exception.MyExceptionBase;
import gao.xiaolei.exception.NoProcessException;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Example;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
import gao.xiaolei.dao.TestProcessDao;
import gao.xiaolei.dao.TestProjectDao;
import gao.xiaolei.entity.TestProcess;
import gao.xiaolei.util.ServiceName;
import gao.xiaolei.util.UuidUtil;

import javax.annotation.Resource;

import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.List;

@Service("TestProcessService")
public class TestProcessService {

    @Resource(name = "TestProcessDao")
    private TestProcessDao testProcessDao;

//    @Resource(name = "QuartzJobService")
//    private QuartzJobService quartzJobService;
//
//    @Resource(name = "TestCopyService")
//    private TestCopyService testCopyService;

    @Autowired
    private RestTemplate restTemplate;

    @Resource(name = "TestProjectDao")
    private TestProjectDao testProjectDao;

    @Resource(name = "CreateNoticeTaskService")
    private CreateNoticeTaskService createNoticeTaskService;

    //获取测试项目的测试流程
    public TestProcess getTestProcessByProject(String project) {
        return testProcessDao.findByToProject(project);
    }

    //判断现在测试项目的测试流程有没有结束
    @Transactional(readOnly = true)
    public boolean getTestProcessIsOver(String project) {
        TestProcess process=testProcessDao.findByToProject(project);
        if (process==null)
            throw new NoProcessException("该项目没有设置测试流程！");
        LocalDateTime endTime = testProcessDao.getEndTime(project);
        return LocalDateTime.now().isAfter(endTime);
    }

    @Transactional
    //给测项目添加测试流程
    public void addProcess(TestProcess testProcess) {
        testProcessDao.save(testProcess);
        if (testProcess.getTrqIsInclude() == 1) {//如果测试流程包括测试需求的话
            createServiceJob(0, testProcess);//就要开启测试需求的定时任务
            createNoticeStageStartTask(testProcess, 0);//到了阶段时间就要通知组员阶段开始的定时任务
        }
        if (testProcess.getDesignIsInclude() == 1) {//如果测试流程包括测试设计的话
            createServiceJob(1, testProcess);//就要开启测试设计的定时任务
            createNoticeStageStartTask(testProcess, 1);//到了阶段时间就要通知组员阶段开始的定时任务
        }
        if (testProcess.getRunIsInclude() == 1) {//如果测试流程包括测试执行的话
            createServiceJob(2, testProcess);//就要开启测试执行的定时任务
            createNoticeStageStartTask(testProcess, 2);//到了阶段时间就要通知组员阶段开始的定时任务
        }
        if (testProcess.getIsNoticeGroupMember() == 1)//如果要将这次的流程通知组员的话
            createProcessStartTask(testProcess);//生成任务通知此次流程安排的信息
    }

    @Transactional
    //修改测试项目流程
    public void updateProcess(TestProcess newTestProcess) {
        TestProcess oldTestProcess1 = testProcessDao.findById(newTestProcess.getId()).orElseThrow(() -> new IllegalArgumentException("不存在该测试流程！"));
        TestProcess oldTestProcess = new TestProcess();
        BeanUtils.copyProperties(oldTestProcess1, oldTestProcess);//如果不先复制后面使用的就不是旧数据了
        testProcessDao.save(newTestProcess);
        LocalDateTime nowTime = LocalDateTime.now().plusMinutes(5);//要在这个流程结束前5分钟前才能修改
        if (oldTestProcess.getTrqIsInclude() == 1) {//如果之前的流程里有测试需求的话
            if (nowTime.isBefore(oldTestProcess.getTrqEndTime())) {//并且在这个测试需求结束前5分钟的话
                //才能移去定时任务
                cancelJob("trqCopy", oldTestProcess.getToProject());//移除副本保存任务
                if (oldTestProcess.getTrqIsNotice() == 1) {//如果之前流程有要到时间通知来给测试需求打分的话
                    cancelJob("trqGradeNotice", oldTestProcess.getToProject());//就要去将这个定时任务取消掉
                    cancelJob("trqStageStartNotice", oldTestProcess.getToProject());//就要去将这个定时任务取消掉
                }
                if (newTestProcess.getTrqIsInclude() == 1) {//如果新的测试流程里有测试需求的话
                    if (newTestProcess.getTrqEndTime().isAfter(LocalDateTime.now()))//如果给的时间是未来时间才去创建定时时间，不然会报错
                        createServiceJob(0, newTestProcess);//就去创建对应的定时任务
                    if (newTestProcess.getTrqBeginTime().isAfter(LocalDateTime.now()))//如果给的时间是未来时间才去创建定时时间，不然会报错
                        createNoticeStageStartTask(newTestProcess, 0);//到了阶段时间就要通知组员阶段开始的定时任务
                }
            }
        }
        if (oldTestProcess.getDesignIsInclude() == 1) {//如果之前的流程里有测试设计的话
            if (nowTime.isBefore(oldTestProcess.getDesignEndTime())) {//并且在这个测试设计结束前5分钟的话
                //才能移去定时任务
                cancelJob("designCopy", oldTestProcess.getToProject());//移除副本保存任务
                if (oldTestProcess.getDesignIsNotice() == 1) {//如果之前流程有要到时间通知来给测试设计打分的话
                    cancelJob("designGradeNotice", oldTestProcess.getToProject());//就要去将这个定时任务取消掉
                    cancelJob("designStageStartNotice", oldTestProcess.getToProject());//就要去将这个定时任务取消掉
                }
                if (newTestProcess.getDesignIsInclude() == 1) {//如果新的测试流程里有测试设计的话
                    if (newTestProcess.getDesignEndTime().isAfter(LocalDateTime.now()))//如果给的时间是未来时间才去创建定时时间，不然会报错
                        createServiceJob(0, newTestProcess);//就去创建对应的定时任务
                    if (newTestProcess.getDesignBeginTime().isAfter(LocalDateTime.now()))//如果给的时间是未来时间才去创建定时时间，不然会报错
                        createNoticeStageStartTask(newTestProcess, 0);//到了阶段时间就要通知组员阶段开始的定时任务
                }
            }
        }
        if (oldTestProcess.getRunIsInclude() == 1) {//如果之前的流程里有测试执行的话
            if (nowTime.isBefore(oldTestProcess.getRunEndTime())) {//并且在这个测试执行结束前5分钟的话
                //才能移去定时任务
                cancelJob("runCopy", oldTestProcess.getToProject());//移除副本保存任务
                if (oldTestProcess.getRunIsNotice() == 1) {//如果之前流程有要到时间通知来给测试执行打分的话
                    cancelJob("runGradeNotice", oldTestProcess.getToProject());//就要去将这个定时任务取消掉
                    cancelJob("runStageStartNotice", oldTestProcess.getToProject());//就要去将这个定时任务取消掉
                }
                if (newTestProcess.getRunIsInclude() == 1) { //如果新的测试流程里有测试执行的话
                    if (newTestProcess.getRunEndTime().isAfter(LocalDateTime.now()))//如果给的时间是未来时间才去创建定时时间，不然会报错
                        createServiceJob(0, newTestProcess);//就去创建对应的定时任务
                    if (newTestProcess.getRunBeginTime().isAfter(LocalDateTime.now()))//如果给的时间是未来时间才去创建定时时间，不然会报错
                        createNoticeStageStartTask(newTestProcess, 0);//到了阶段时间就要通知组员阶段开始的定时任务
                }
            }
        }
        if (newTestProcess.getIsNoticeGroupMember() == 1)//如果要将这次的流程通知组员的话
            createProcessStartTask(newTestProcess);//生成任务通知此次流程安排的信息
    }

//    //创建定时任务
//    private void createJob(String key, String group, Map<String, Object> dataMap, LocalDateTime doTime, Class<? extends Job> jobClass, String description) throws SchedulerException {
//        JobKey jobKey = JobKey.jobKey(key, group);
//        TaskDefine taskDefine = TaskDefine.builder()
//                .jobKey(jobKey)
//                .cronExpression(CronUtil.getCron(doTime))   //定时任务 的cron表达式
//                .jobClass(jobClass)   //定时任务 的具体执行逻辑
//                .description(description)    //定时任务 的描述
//                .jobDataMap(dataMap)
//                .build();
//        quartzJobService.scheduleJob(taskDefine);
//    }

    //取消定时任务
    private void cancelJob(String key, String project) {
        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_JSON);
        HttpEntity<JobkeyDto> httpEntity = new HttpEntity<>(new JobkeyDto(key, project, 0), headers);
        restTemplate.postForEntity(ServiceName.BUS_SERVICE.getServiceName().concat("/message/quertz"), httpEntity, String.class);
    }

    //创建具体的定时任务
    private void createServiceJob(int type, TestProcess testProcess) {
        if (type == 0) {
            //开启到了测试需求的结束时间就要保存副本的定时任务
            CopyJobInDto copyJobInDto = new CopyJobInDto(testProcess.getToProject(), "trqCopy", "在".concat(testProcess.getTrqEndTime().toString()).concat("时将项目(").concat(testProcess.getToProject()).concat(")测试需求审核通过的保存一个副本"), testProcess.getTrqEndTime(), type);
            callQuertzCreateCopyTimingJob(copyJobInDto);
            if (testProcess.getTrqIsNotice() == 1) {//如果测试需求到了时间要通知老师来打分的话
                //就要开启到了测试需求的结束时间就要通知老师来打分的定时任务
                createNoticeGradeTask(testProcess, 0);
            }
        } else if (type == 1) {
            //开启到了测试设计的结束时间就要保存副本的定时任务
            CopyJobInDto copyJobInDto = new CopyJobInDto(testProcess.getToProject(), "designCopy", "在".concat(testProcess.getDesignEndTime().toString()).concat("时将项目(").concat(testProcess.getToProject()).concat(")测试设计审核通过的保存一个副本"), testProcess.getDesignEndTime(), type);
            callQuertzCreateCopyTimingJob(copyJobInDto);
            if (testProcess.getDesignIsNotice() == 1) {//如果测试设计到了时间要通知老师来打分的话
                //就要开启到了测试设计的结束时间就要通知老师来打分的定时任务
                createNoticeGradeTask(testProcess, 1);
            }
        } else if (type == 2) {
            //就要开启到了测试执行的结束时间就要保存副本的定时任务
            CopyJobInDto copyJobInDto = new CopyJobInDto(testProcess.getToProject(), "runCopy", "在".concat(testProcess.getRunEndTime().toString()).concat("时将项目(").concat(testProcess.getToProject()).concat(")测试执行审核通过的保存一个副本"), testProcess.getRunEndTime(), type);
            callQuertzCreateCopyTimingJob(copyJobInDto);
            if (testProcess.getRunIsNotice() == 1) {//如果测试执行到了时间要通知老师来打分的话
                //就要开启到了测试执行的结束时间就要通知老师来打分的定时任务
                createNoticeGradeTask(testProcess, 2);
            }
        }
//        else if (type == 3) {
//            //开启到了测试报告的结束时间就要保存副本的定时任务
//            CopyJobInDto copyJobInDto = new CopyJobInDto(testProcess.getToProject(), "reportCopy", "在".concat(testProcess.getReportEndTime().toString()).concat("时将项目(").concat(testProcess.getToProject()).concat(")测试报告审核通过的保存一个副本"), testProcess.getReportEndTime(), type);
//            callQuertzCreateCopyTimingJob(copyJobInDto);
//            if (testProcess.getReportIsNotice() == 1) {//如果测试报告到了时间要通知老师来打分的话
//                //就要开启到了测试报告的结束时间就要通知老师来打分的定时任务
//                createNoticeGradeTask(testProcess, 3);
//            }
//        }
    }

    //生成任务通知项目所在的小组流程安排
    private void createProcessStartTask(TestProcess testProcess) {
        String projectName = testProjectDao.getName(testProcess.getToProject()).orElse("被删除");
        String groupId = testProjectDao.getGroupId(testProcess.getToProject()).orElseThrow(() -> new IllegalArgumentException("项目".concat(projectName).concat("(").concat(testProcess.getToProject()).concat(")不属于任何组！")));
        DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
        StringBuilder description = new StringBuilder();
        description = description
                .append(projectName)
                .append("的测试流程安排如下:<br>")
                .append("总安排时间:")
                .append(testProcess.getTotalBeginTime().format(dateTimeFormatter))
                .append("~")
                .append(testProcess.getTotalEndTime().format(dateTimeFormatter));
        if (testProcess.getTrqIsInclude() == 1) {
            description = description
                    .append("<br>测试需求:")
                    .append(testProcess.getTrqBeginTime().format(dateTimeFormatter))
                    .append("~")
                    .append(testProcess.getTrqEndTime().format(dateTimeFormatter));
        }
        if (testProcess.getDesignIsInclude() == 1) {
            description = description
                    .append("<br>测试设计:")
                    .append(testProcess.getDesignBeginTime().format(dateTimeFormatter))
                    .append("~")
                    .append(testProcess.getDesignEndTime().format(dateTimeFormatter));
        }
        if (testProcess.getRunIsInclude() == 1) {
            description = description
                    .append("<br>测试执行:")
                    .append(testProcess.getRunBeginTime().format(dateTimeFormatter))
                    .append("~")
                    .append(testProcess.getRunEndTime().format(dateTimeFormatter));
        }
//        if (testProcess.getReportIsInclude() == 1) {
//            description = description
//                    .append("<br>测试报告:")
//                    .append(testProcess.getReportBeginTime().format(dateTimeFormatter))
//                    .append("~")
//                    .append(testProcess.getReportEndTime().format(dateTimeFormatter));
//        }
        List<String> receiverList = restTemplate.getForObject(ServiceName.AUTH_SERVICE.getServiceName().concat("/user/groupMember/").concat(groupId), List.class);
        Task task = Task.builder()
                .id(UuidUtil.get32Uuid())
                .name("流程安排通知")
                .responsible("e3ee20f600cc4e9bb901275787b88692")//系统通知的发件人都是管理员
                .isAttachment(0)
                .isGroup(0)
                .status(1)
                .type(4)
                .priority(0)
                .description(description.toString())
                .build();
        createNoticeTaskService.createNoticeTask(task, receiverList);
    }

    //生成定时通知老师来打分的定时任务
    private void createNoticeGradeTask(TestProcess testProcess, int type) {
        String projectName = testProjectDao.getName(testProcess.getToProject()).orElse("被删除");
        String groupId = testProjectDao.getGroupId(testProcess.getToProject()).orElseThrow(() -> new IllegalArgumentException("项目".concat(projectName).concat("(").concat(testProcess.getToProject()).concat(")不属于任何组！")));
        String groupName = restTemplate.getForObject(ServiceName.AUTH_SERVICE.getServiceName().concat("/user/groupName/").concat(groupId), String.class);
        String receiver = restTemplate.getForObject(ServiceName.AUTH_SERVICE.getServiceName().concat("/user/groupLeadingCadre/").concat(groupId), String.class);
        List<String> receiverList = new ArrayList<>(1);
        receiverList.add(receiver);
        String name = null, key = null;
        LocalDateTime startTime = null;
        switch (type) {
            case 0:
                name = "测试需求打分";
                key = "trqGradeNotice";
                startTime = testProcess.getTrqEndTime();
                break;
            case 1:
                name = "测试设计打分";
                key = "designGradeNotice";
                startTime = testProcess.getDesignEndTime();
                break;
            case 2:
                name = "测试执行打分";
                key = "runGradeNotice";
                startTime = testProcess.getRunEndTime();
                break;
//            case 3:
//                name = "测试报告打分";
//                key = "reportGradeNotice";
//                startTime = testProcess.getReportEndTime();
//                break;
        }
        Task task = Task.builder()
                .id(UuidUtil.get32Uuid())
                .name(name)
                .responsible("e3ee20f600cc4e9bb901275787b88692")//系统通知的发件人都是管理员
                .isAttachment(0)
                .isGroup(0)
                .status(1)
                .type(4)
                .priority(0)
                .description("请去给".concat(groupName).concat("的").concat(projectName).concat(name))
                .build();
        TaskJobInDto taskJobInDto = new TaskJobInDto(testProcess.getToProject(), key, "在"
                .concat(startTime.toString())
                .concat("时通知")
                .concat(receiverList.size() > 0 ? receiverList.get(0) : receiverList.toString())
                .concat("去给项目(")
                .concat(testProcess.getToProject())
                .concat(")的")
                .concat(name), startTime, new TaskInDto(task, receiverList));
        callQuertzCreateSendTaskTimingJob(taskJobInDto);
    }

    //生成定时通知组员流程的某个阶段开始了
    private void createNoticeStageStartTask(TestProcess testProcess, int type) {
        String projectName = testProjectDao.getName(testProcess.getToProject()).orElse("被删除");
        String groupId = testProjectDao.getGroupId(testProcess.getToProject()).orElseThrow(() -> new IllegalArgumentException("项目".concat(projectName).concat("(").concat(testProcess.getToProject()).concat(")不属于任何组！")));
        List<String> receiverList = restTemplate.getForObject(ServiceName.AUTH_SERVICE.getServiceName().concat("/user/groupMember/").concat(groupId), List.class);
        DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
        String name = null, key = null, duration = "";
        LocalDateTime startTime = null;
        switch (type) {
            case 0:
                name = "测试需求开始";
                key = "trqStageStartNotice";
                startTime = testProcess.getTrqBeginTime();
                duration = duration.concat(testProcess.getTrqBeginTime().format(dateTimeFormatter).concat("~").concat(testProcess.getTrqEndTime().format(dateTimeFormatter)));
                break;
            case 1:
                name = "测试设计开始";
                key = "designStageStartNotice";
                startTime = testProcess.getDesignBeginTime();
                duration = duration.concat(testProcess.getDesignBeginTime().format(dateTimeFormatter).concat("~").concat(testProcess.getDesignEndTime().format(dateTimeFormatter)));
                break;
            case 2:
                name = "测试执行开始";
                key = "runStageStartNotice";
                startTime = testProcess.getRunBeginTime();
                duration = duration.concat(testProcess.getRunBeginTime().format(dateTimeFormatter).concat("~").concat(testProcess.getRunEndTime().format(dateTimeFormatter)));
                break;
//            case 3:
//                name = "测试报告开始";
//                key = "reportStageStartNotice";
//                startTime = testProcess.getReportBeginTime();
//                duration = duration.concat(testProcess.getReportBeginTime().format(dateTimeFormatter).concat("~").concat(testProcess.getReportEndTime().format(dateTimeFormatter)));
//                break;
        }
        Task task = Task.builder()
                .id(UuidUtil.get32Uuid())
                .name(name)
                .responsible("e3ee20f600cc4e9bb901275787b88692")//系统通知的发件人都是管理员
                .isAttachment(0)
                .isGroup(0)
                .status(1)
                .type(4)
                .priority(0)
                .description(projectName.concat(name).concat("了").concat(",请在(").concat(duration).concat(")时间段内完成任务"))
                .build();
        StringBuilder receiverName = new StringBuilder();
        for (int i = 0, length = receiverList.size(); i < length; i++) {
            receiverName.append(receiverList.get(i));
        }
        TaskJobInDto taskJobInDto = new TaskJobInDto(testProcess.getToProject(), key, "在"
                .concat(startTime.toString())
                .concat("时通知")
                .concat(receiverName.toString())
                .concat("项目(")
                .concat(testProcess.getToProject())
                .concat(")的")
                .concat(name), startTime, new TaskInDto(task, receiverList));
        callQuertzCreateSendTaskTimingJob(taskJobInDto);
    }

    //向定时服务发送生成测试副本的定时任务
    private void callQuertzCreateCopyTimingJob(CopyJobInDto copyJobInDto) {
        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_JSON_UTF8);
        HttpEntity<CopyJobInDto> httpEntity = new HttpEntity<>(copyJobInDto, headers);
        restTemplate.postForObject(ServiceName.QUERTZ_SERVICE.getServiceName().concat("/testCopy"), httpEntity, Object.class);
    }

    //向定时服务发送生成向某人发送任务的定时任务
    private void callQuertzCreateSendTaskTimingJob(TaskJobInDto taskJobInDto) {
        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_JSON_UTF8);
        HttpEntity<TaskJobInDto> httpEntity = new HttpEntity<>(taskJobInDto, headers);
        restTemplate.postForObject(ServiceName.QUERTZ_SERVICE.getServiceName().concat("/sendTask"), httpEntity, Object.class);
    }
}
